﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Threading;
using System.ComponentModel;
using System.Windows.Browser;
using System.IO.IsolatedStorage;
using System.IO;

namespace LargeFileUpload
{
    /// <summary>
    /// Chunked File Uploader Example in Silverlight make the following assumptions
    ///     1. Does not handle security on the file upload or set the upload location on the web server
    ///     2. Does not handle errors when communicating with the web server (just cancels)
    ///     3. Does currently provide the ability to pause/resume but not if you shut it down
    ///     4. Has the beginnings of the code necessary to pause/resume between shut downs.
    ///     5. Does not negotiate with the web page to determine a temp name for the file (like a GUID or something)
    ///     
    /// </summary>
    public partial class MainPage : UserControl
    {
        [ScriptableMember]
        public event EventHandler<ProgressUpdateEvent> jsProgressUpdateEvent;

        [ScriptableMember]
        public int MaxUploadThreads { get; set; }

        public static int Offset;
        public static bool Paused;
        public static string FileName;
        public long bytesReadTotal = 0;
        public static System.IO.FileStream fs;
        public static string LastErrorMessage = "";
        private const long READ_BUFFER_LEN = 57344;
        private const string TRANSFER_FILE = @"Outbox\JunkFile";
        private BackgroundWorker bw = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true };


        public MainPage()
        {
            InitializeComponent();
            HtmlPage.RegisterScriptableObject("SilverlightApp", this);
            MaxUploadThreads = MaxUploadThreads == 0 ? 1 : MaxUploadThreads;

            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

            bw.ProgressChanged += (sender, e) => {

                if (LastErrorMessage.Length > 0)
                {
                    txtStatus.Text = LastErrorMessage;  // we will clear LastErrorMessage on Resume
                    cmdSendFile.Content = "Resume";
                    if (null != jsProgressUpdateEvent) jsProgressUpdateEvent(this, new ProgressUpdateEvent() { BytesReadTotal = 0, BytesRead = 0, ErrorMessage = LastErrorMessage, StateText = "Error" });
                }
                else
                {
                    txtStatus.Text = String.Format("Transferred {0} out of {1}"
                        , FileSizeString(READ_BUFFER_LEN * (Offset + 1))
                        , FileSizeString(fs.Length));
                    progressBar.Value = e.ProgressPercentage;
                    if (null != jsProgressUpdateEvent) jsProgressUpdateEvent(this, new ProgressUpdateEvent() { BytesReadTotal = READ_BUFFER_LEN * (Offset + 1), BytesRead = (int)READ_BUFFER_LEN, ErrorMessage = "", StateText = "Uploading" });
                }

            };

            bw.DoWork += (sender, e) => { UploadFileToWeb((string)e.Argument, (BackgroundWorker)sender, e); }; 
        }

        void UploadFileToWeb(string fileDetails, BackgroundWorker w, DoWorkEventArgs e)
        {
            byte[] b = new byte[READ_BUFFER_LEN];
            int bytesRead = fs.Read(b, 0, (int)READ_BUFFER_LEN);
            int uploads_running = 1;
            while (bytesRead > 0)
            {
                AutoResetEvent a = new  AutoResetEvent(false);
                WebClient wc = new WebClient();
                wc.Headers["Content-Type"] = "application/x-www-form-urlencoded";
                wc.UploadStringCompleted += (s, e_UploadStringCompleted) =>
                {
                    if (e_UploadStringCompleted.Error != null)
                    {
                        lock(this)
                        {
                            LastErrorMessage = e_UploadStringCompleted.Error.Message.ToString();
                            Paused = true;
                            Offset -= uploads_running;
                            if (Offset < 0 )
                                Offset = 0;
                        }
                    }
                    else
                    {
                        // need to ensure Thread Safety before allowing the following code
                        //fileBitsTracking(Convert.ToInt64(e_UploadStringCompleted.Result));  // Current Offset
                    }
                    Interlocked.Decrement(ref uploads_running);
                    a.Set();
                };

                wc.UploadStringAsync(new Uri("/StoreFile.ashx", UriKind.Relative), "POST"
                    , "fileinfo=+" + fileDetails + "&offset=" + Offset.ToString() + "&filestream=" 
                        + Convert.ToBase64String(b, 0, bytesRead));

                if (MaxUploadThreads == uploads_running)
                    a.WaitOne();

                // check for errors and move the filestream position backwards if there was one
                if (LastErrorMessage.Length > 0)
                    fs.Seek(READ_BUFFER_LEN * Offset, SeekOrigin.Begin);
                else
                {
                    Interlocked.Increment(ref uploads_running);
                    Interlocked.Increment(ref Offset);
                }
                w.ReportProgress((int)((float)(READ_BUFFER_LEN * (Offset + 1)) / (float)fs.Length * 100));

                while (true == Paused && !w.CancellationPending)
                    Thread.Sleep(100);

                if (w.CancellationPending || e.Cancel)
                {
                    e.Cancel = true;
                    break;  // bw request is stopping
                }
                bytesRead = fs.Read(b, 0, (int)READ_BUFFER_LEN);
            }
        }

        
        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled == true))
            {
                bytesReadTotal = 0; // reset 
                progressBar.Value = 0;
                txtStatus.Text = "Upload Canceled!";
                cmdSendFile.Content = "Select File";
                if (null != jsProgressUpdateEvent) jsProgressUpdateEvent(this, new ProgressUpdateEvent() { BytesReadTotal = 0, BytesRead = 0, ErrorMessage = "", StateText = "Canceled" });
            }
            else if (!(e.Error == null))
            {
                txtStatus.Text = ("Error: " + e.Error.Message);
                cmdSendFile.Content = "Select File";
                if (null != jsProgressUpdateEvent) jsProgressUpdateEvent(this, new ProgressUpdateEvent() { BytesReadTotal = 0, BytesRead = 0, ErrorMessage = e.Error.Message, StateText = "Error" });
            }
            else
            {
                bytesReadTotal = 0;
                progressBar.Value = 100;
                cmdCancel.IsEnabled = false;
                txtStatus.Text = "File upload completed!";
                cmdSendFile.Content = "Select File";
                if (null != jsProgressUpdateEvent) jsProgressUpdateEvent(this, new ProgressUpdateEvent() { BytesReadTotal = 0, BytesRead = 0, ErrorMessage = "", StateText = "Completed" });
            }


        }

        /*
        private void fileBitsSetup(long fileSize)
        {
            long howManyFileParts = (long)(fileSize / READ_BUFFER_LEN);
            howManyFileParts += fileSize % READ_BUFFER_LEN > 0 ? 1 : 0; // check add the excess
            string bitsOfFile = (new string('0', (int)howManyFileParts));

            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (isf.FileExists(TRANSFER_FILE)) isf.DeleteFile(TRANSFER_FILE);
                using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(TRANSFER_FILE, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write, isf))
                {
                    using (StreamWriter sw = new StreamWriter(isfs))
                    {
                        sw.Write(bitsOfFile);
                        sw.Close();
                    }
                }
            }
        }

        static void fileBitsTracking(long bitsOffset)
        {
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(TRANSFER_FILE, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write, isf))
                {
                    isfs.Seek(bitsOffset, SeekOrigin.Begin);
                    using (StreamWriter sw = new StreamWriter(isfs))
                    {
                        sw.Write('1');
                        sw.Close();
                    }
                }
            }
        }
        */

        private void cmdCancel_Click(object sender, RoutedEventArgs e)
        {
            Paused = false;
            bw.CancelAsync();
            cmdCancel.IsEnabled = false;
            cmdSendFile.Content = "Select File";
        }


        private void cmdSendFile_Click(object sender, RoutedEventArgs e)
        {
            if (cmdSendFile.Content.ToString() == "Pause")
            {
                Paused = true;
                cmdSendFile.Content = "Resume";
                return;
            }

            if (cmdSendFile.Content.ToString() == "Resume")
            {
                LastErrorMessage = "";
                Paused = false;
                cmdSendFile.Content = "Pause";
                return;
            }

            OpenFileDialog f = new OpenFileDialog();
            bool? userClickedOK = f.ShowDialog();
            if (userClickedOK == true)
            {
                cmdSendFile.Content = "Pause";

                Offset = 0;
                progressBar.Value = 0;
                progressBar.Maximum = 100;

                fs = f.File.OpenRead();
                FileName = f.File.Name;
                txtStatus.Text = FileName;

                //fileBitsSetup(f.File.Length);

                cmdCancel.IsEnabled = true;
                bw.RunWorkerAsync(FileName); // calls DoWork()
            }
        }

        private string FileSizeString(long bytes)
        {
            if (bytes > 1073741824) return String.Format("{0:0.00} GB", (float)bytes / (float)1073741824);
            else if (bytes > 1048576) return String.Format("{0:0.00} MB", (float)bytes / (float)1048576);
            else if (bytes > 1024) return String.Format("{0:0.00} KB", (float)bytes / (float)1024);
            else return String.Format("{0:0.00} Bytes", bytes);
        }

    }


    [ScriptableType]
    public class ProgressUpdateEvent : EventArgs
    {
        public long BytesReadTotal { get; set; }
        public int BytesRead { get; set; }
        public string ErrorMessage { get; set; }
        public string StateText { get; set; }

    }
}